package com.lcx.util;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 反射类
 */
public class ReflectUtil {

    private static final ThreadLocal<Map<String,Object>> threadLocal = new ThreadLocal<>();

    /**
     * 子类重写属性KEY
     */
    private static final String CHILD_FIELD="_childField";
    /**
     * 递归栈深度计数器KEY
     */
    private static final String CHILD_FIELD_COUNT="_childFieldCount";

    private static final String CHILD_METHOD="_childMethod";

    private static final String CHILD_METHOD_COUNT="_childMethodCount";

    /**
     * 获取类中的所有属性，包括继承
     * 重写后的类不会重新出现在
     * @return
     */
    public static List<Field> getFields(Class<?> clazz){
        Map<String,String> hasFields = getProperty(CHILD_FIELD,Map.class,new HashMap<String,String>());
        int count = getProperty(CHILD_FIELD_COUNT,int.class,0);

        Field[] fields = clazz.getDeclaredFields();
        List<Field> list = new ArrayList<>(10);
        if(fields != null){
            for(Field f:fields){
                //过滤子类重写属性在父类中
                if(hasFields.get(f.getName())==null) list.add(f);
                hasFields.put(f.getName(),"1");
            }
        }
        Class superClazz = clazz.getSuperclass();
        if(superClazz!=null){
            putProperty(CHILD_FIELD_COUNT,++count);
            list.addAll(getFields(superClazz));
            putProperty(CHILD_FIELD_COUNT,--count);
        }
        if(count == 0){
            clearProperty(CHILD_FIELD);
            clearProperty(CHILD_FIELD_COUNT);
        }
        return list;
    }

    /**
     * 获取类中的所有方法，包括继承
     * 重写后的类不会重新出现在
     * @return
     */
    public static List<Method> getMethods(Class<?> clazz){
        Map<String,String> hasMethods = getProperty(CHILD_METHOD,Map.class,new HashMap<String,String>());
        int count = getProperty(CHILD_METHOD_COUNT,int.class,0);
        Method[] methods = clazz.getDeclaredMethods();
        List<Method> list = new ArrayList<>(10);
        if(methods != null){
            String methodId;
            Class<?> [] params;
            for(Method m:methods){
                methodId=clazz.getName()+"."+m.getName();
                params = m.getParameterTypes();
                if(params!=null){
                    for(Class<?> c:params){
                        methodId+=","+c.getName();
                    }
                }
                if(hasMethods.get(methodId) == null) list.add(m);
                hasMethods.put(methodId,"1");
            }
        }
        Class superClazz = clazz.getSuperclass();
        if(superClazz!=null){
            putProperty(CHILD_METHOD_COUNT,++count);
            list.addAll(getMethods(superClazz));
            putProperty(CHILD_METHOD_COUNT,--count);
        }
        if(count == 0){
            clearProperty(CHILD_METHOD);
            clearProperty(CHILD_METHOD_COUNT);
        }
        return list;
    }

    /**
     *
     * @param clazz
     * @param name
     * @return
     */
    public static List<Method> getPublicMethodByName(Class<?> clazz,String name){
        Method []methods = clazz.getMethods();
        List<Method> list = new LinkedList<>();
        if(methods!=null){
            for(Method m:methods){
                if(m.getName().equals(name)){
                    list.add(m);
                }
            }
        }
        return list;
    }

    /**
     * 依据属性名获取属性
     * @param name
     * @return
     */
    public static Field getFieldByName(String name,Class<?> clazz){
        Field field = null;
        try{
            field = clazz.getDeclaredField(name);
        }catch (Exception e){
        }
        if(field == null){
            Class superClazz = clazz.getSuperclass();
            if(superClazz!=null){
                field = getFieldByName(name,superClazz);
            }
        }
        return field;
    }

    public static Method getReadMethodByField(Field f) throws Exception{
        PropertyDescriptor pd = new PropertyDescriptor(f.getName(), f.getDeclaringClass());
        return pd.getReadMethod();
    }

    public static Method getWriteMethodByField(Field f) throws Exception{
        PropertyDescriptor pd = new PropertyDescriptor(f.getName(), f.getDeclaringClass());
        return pd.getWriteMethod();
    }

    public static Object invokeMethod(Class<?> clazz,Object instance,String methodName,Class<?> []types,Object...params){
        if(clazz == null) return null;
        try{
            Method m = clazz.getDeclaredMethod(methodName,types);
            return m.invoke(instance,params);
        }catch (Exception e){
        }
        return invokeMethod(clazz.getSuperclass(),instance,methodName,types,params);
    }


    private static <T>T getProperty (String key,Class<T> clazz,T def){
        Map<String,Object> property = threadLocal.get();
        T val=null;
        if(property != null){
            val = (T) property.get(key);
        }
        if(val == null && def != null){
            putProperty(key,def);
        }
        return val==null?def:val;
    }

    private static void putProperty(String key,Object val){
        Map<String,Object> property = threadLocal.get();
        if(property == null){
            property = new HashMap<>();
            threadLocal.set(property);
        }
        property.put(key,val);
    }

    private static void clearProperty(String key){
        Map<String,Object> property = threadLocal.get();
        if(property != null){
            property.remove(key);
            if(property.isEmpty()){
                threadLocal.remove();
            }
        }
    }
}
